package com.chinamobile.iot.mqtt.spi.impl;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;


/**
 * mqtt subscription tree class
 * <p>
 * this class receive all subscription and convert these subscriptions to a tree.
 * this tree can be use with mqtt broker to process subscriptions information.
 * this tree can be use in multi thread enviroment
 * <p>
 * the same client can't add subscription at the same time.
 *
 * @Author shu zhongling
 * @mail 27092348@qq.com
 * <p>
 * this is code is public for free, you can use it anywhere
 */
public class SubscriptionTree<T> {
    private final static String MULTI_LEVEL = "#";
    private final static String SINGLE_LEVEL = "+";

    /**
     * the root of the subscription tree, the token is default 'root'
     */
    private final TreeNode<T> root = new TreeNode<>("root");
    /**
     * Map(T -> map(topic, qos))
     */
    private final ConcurrentHashMap<T, ConcurrentHashMap<String, Integer>> mapClient = new ConcurrentHashMap<>();

    /**
     * 当clean操作进行时，不允许add、match、toString等操作
     */
    private final ReentrantReadWriteLock cleanLock = new ReentrantReadWriteLock();

    public SubscriptionTree() {
    }

    /**
     * round the subscription tree and get the subscription info, one line is a subscription
     *
     * @return
     */
    public String toString() {
        try {
            cleanLock.readLock().lock();
            return round("", root);
        } finally {
            cleanLock.readLock().unlock();
        }
    }

    /**
     * called by toString
     *
     * @param topic topic before round the node
     * @param node  current tree node
     * @return the string of the whole tree after round node
     */
    private String round(String topic, final TreeNode<T> node) {
        StringBuilder builder = new StringBuilder();
        String result;

        if (!node.getToken().equals("root")) {
            if (topic.equals("")) {
                topic = node.token;
            } else {
                topic = topic + "/" + node.getToken();
            }
        }

        result = printTopicFilter(topic, node.getClients());
        if (result != null) {
            builder.append(result);
        }

        if (node.getMulti() != null) {
            result = round(topic, node.getMulti());
            builder.append(result);
        }
        if (node.getSingle() != null) {
            result = round(topic, node.getSingle());
            builder.append(result);
        }

        if (node.getChilds() != null) {
            ConcurrentHashMap<String, TreeNode<T>> tmp = new ConcurrentHashMap<>();
            tmp.putAll(node.getChilds());
            Iterator<Map.Entry<String, TreeNode<T>>> iterator = tmp.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, TreeNode<T>> entry = iterator.next();
                TreeNode<T> n = entry.getValue();
                result = round(topic, n);
                builder.append(result);
            }
        }
        return builder.toString();
    }

    /**
     * called by the round function, use to produce the result string
     *
     * @param topic  the topic
     * @param client the client who subscribe the topic
     * @return the result string, one line represent a client subscription
     */
    private String printTopicFilter(String topic, ConcurrentHashMap<T, Integer> client) {
        if (client == null) {
            return null;
        }
        StringBuilder builder = new StringBuilder(128);
        client.forEach((k, v) -> {
            builder.append(topic);
            builder.append(",");
            builder.append(k);
            builder.append(",");
            builder.append(v);
            builder.append("\n");
        });
        return builder.toString();
    }

    public ConcurrentHashMap<String, Integer> getSubscription(T client) {
        return mapClient.get(client);
    }


    /**
     * add a new subscription to the tree
     *
     * @param topicFilter the subscript topic filter
     * @param clientId    the client who subscribe the topic filter
     * @param qos         the QoS of this subscription
     * @return if succeed, return old qos this topic filter if it exists, return 3 if this topic  filter dons't exists
     *          if failed return -1, may be the filter format is error
     */
    public int add(String topicFilter, T clientId, int qos) {
        try {
            cleanLock.readLock().lock();
            String[] levels = topicFilter.split("/");
            int size = levels.length;
            TreeNode<T> current = root;
            for (int i = 0; i < size; i++) {
                switch (levels[i]) {
                    case MULTI_LEVEL: {
                        if (i != size - 1) {
                            // 多级匹配#只能出现在filter的尾部，如果不是尾部，直接返回
                            return -1;
                        }
                        if (current.getMulti() != null) {
                            addClient(current.getMulti(), clientId, qos);
                        } else {
                            synchronized (current) {
                                if (current.getMulti() == null) {
                                    TreeNode<T> temp = new TreeNode<>(MULTI_LEVEL);
                                    current.setMulti(temp);
                                }
                            }
                            addClient(current.getMulti(), clientId, qos);
                        }
                        //return oldQos;
                    }
                    case SINGLE_LEVEL: {
                        if (current.getSingle() != null) {
                            if (i == size - 1) {
                                addClient(current.getSingle(), clientId, qos);
                                break;
                            }
                            current = current.getSingle();
                        } else {
                            synchronized (current) {
                                if (current.getSingle() == null) {
                                    TreeNode<T> temp = new TreeNode<>(SINGLE_LEVEL);
                                    current.setSingle(temp);
                                }
                            }
                            if (i == size - 1) {
                                addClient(current.getSingle(), clientId, qos);
                                break;
                            }
                            current = current.getSingle();
                        }
                    }
                    break;
                    default: {
                        TreeNode<T> node = current.getChilds().computeIfAbsent(levels[i], TreeNode::new);
                        if (i == size - 1) {
                            addClient(node, clientId, qos);
                            break;
                        } else {
                            current = node;
                        }
                    }
                }
            }
            // 记录某个用户订阅了某个topic，在后续清除用户订阅时，可以清除掉该topic
            return recordClientSubscribe(clientId, topicFilter, qos);
        } finally {
            cleanLock.readLock().unlock();
        }
    }


    /**
     * record client who subscribe topic, called by the add function
     *
     * @param clientId client
     * @param topic    topic filter that the client subscribe
     *                 0 the client hasn't subscribe the topic before
     * @return the old qos the this topic,
     * 0 or 1 means old subscription topic filter exists
     * 3 means no old subscription topic filter exists
     */
    private int recordClientSubscribe(T clientId, String topic, int qos) {
        ConcurrentHashMap<String, Integer> map = mapClient.computeIfAbsent(clientId, k -> new ConcurrentHashMap<>(3));
        Integer old = map.put(topic, qos);
        if (old != null) {
            return old;
        }
        else{
            return 3;
        }
    }

    /**
     * add some client to the leaf TreeNode, if the client also exists in the leaf's client list, then nothing will happen
     *
     * @param leaf     the leaf TreeNode
     * @param clientId the client of the TreeNode
     * @param qos      the qos of the subscription
     */
    private void addClient(final TreeNode<T> leaf, T clientId, Integer qos) {
        leaf.addClient(clientId, qos);
    }

    /**
     * get all the client that subscribe the @param filter
     * @param topicFilter the filter
     * @return the clients that subscribe the filter
     */
    public LinkedList<ClientInfo<T>> getClients(String topicFilter){
        LinkedList<ClientInfo<T>> clients = new LinkedList<>();
        try {
            String[] levels = topicFilter.split("/");
            int size = levels.length;
            TreeNode<T> current = root;
            cleanLock.readLock().lock();
            for (int i = 0; i < size; i++) {
                switch (levels[i]) {
                    case MULTI_LEVEL: {
                        if (current.getMulti() != null) {
                            if (i == size - 1) {
                                if (current.getMulti().getClients() != null) {
                                    if (!current.getMulti().getClients().isEmpty()) {
                                        current.getMulti().getClients().forEach((k, v) -> {
                                            addToSet(k, v, topicFilter, clients);
                                        });
                                    }
                                }
                            }
                        }
                        return clients;
                    }
                    case SINGLE_LEVEL: {
                        if (current.getSingle() != null) {
                            if (i == size - 1) {
                                if (current.getSingle().getClients() != null) {
                                    if (!current.getSingle().getClients().isEmpty()) {
                                        current.getSingle().getClients().forEach((k, v) -> {
                                            addToSet(k, v, topicFilter, clients);
                                        });
                                    }
                                }
                                return clients;
                            } else {
                                current = current.getSingle();
                            }
                        } else {
                            return clients;
                        }
                    }
                    break;
                    default: {
                        TreeNode<T> node = current.getChilds().get(levels[i]);
                        if (node != null) {
                            if (i == size - 1) {
                                if (node.getClients() != null) {
                                    if (!node.getClients().isEmpty()) {
                                        node.getClients().forEach((k,v) -> {
                                            addToSet(k, v, topicFilter, clients);
                                        });
                                    }
                                }
                                return clients;
                            }
                            current = node;
                        } else {
                            return clients;
                        }
                    }
                }
            }
        } finally {
            cleanLock.readLock().unlock();
        }
        return clients;
    }

    private void addToSet(T k, int qos, String filter, List<ClientInfo<T>> result){
        ClientInfo<T> clientInfo = new ClientInfo<T>();
        clientInfo.setFilter(filter);
        clientInfo.setClient(k);
        clientInfo.setQos(qos);
        result.add(clientInfo);
    }

    /**
     * check if any subscription match the topic. if a mqtt client publish a message with a topic,
     * then the platform use this method to check if any subscription filter match.
     * <p>
     * this method only return client info, don't return the filter
     * matchFull will return client info with the filter
     *
     * @param newTopic the topic of pulish message
     * @return the client collection that match the newTopic
     */
    public Set<ClientInfo<T>> match(String newTopic) {

        try {
            String[] levels = newTopic.split("/");
            int size = levels.length;
            TreeNode<T> current;
            final HashMap<ClientInfo<T>, Integer> result = new HashMap<>();
            ArrayList<TreeNode<T>> currentTarget = new ArrayList<>();
            ArrayList<TreeNode<T>> tempTarget = new ArrayList<>();

            cleanLock.readLock().lock();

            currentTarget.add(root);

            for (int i = 0; i < size; i++) {
                for (int j = 0; j < currentTarget.size(); j++) {
                    current = currentTarget.get(j);

                    if (current.getMulti() != null) {
                        addResult(result, current.getMulti(), null);
                    }
                    if (current.getSingle() != null) {
                        if (i == size - 1) {
                            addResult(result, current.getSingle(), null);
                        } else {
                            tempTarget.add(current.getSingle());
                        }
                    }
                    TreeNode<T> n = current.getChilds().get(levels[i]);
                    if (n != null) {
                        if (i == size - 1) {
                            addResult(result, n, null);
                            if (n.getMulti() != null) {
                                addResult(result, n.getMulti(), null);
                            }
                        } else {
                            tempTarget.add(n);
                        }
                    }
                }
                currentTarget.clear();
                ArrayList<TreeNode<T>> temp = currentTarget;
                currentTarget = tempTarget;
                tempTarget = temp;
            }
            return result.keySet();
        } finally {
            cleanLock.readLock().unlock();
        }
    }

    /**
     * check if any client subscribe the topic filter
     * this method will return the client and the filter, the method match just return the client
     *
     * @param newTopic the topic
     * @return the client info set  with each client and filter
     */
    public Set<ClientInfo<T>> matchFull(String newTopic) {
        try {
            String[] levels = newTopic.split("/");
            int size = levels.length;
            TreeNode<T> current;
            String filter = "";
            String tempFilter = "";
            final HashMap<ClientInfo<T>, Integer> result = new HashMap<>();
            ArrayList<TreeNode<T>> currentTarget = new ArrayList<>();
            ArrayList<TreeNode<T>> tempTarget = new ArrayList<>();
            // used to return filter at the clientInfo
            ArrayList<String> lstFilter = new ArrayList<>();
            ArrayList<String> tempLstFilter = new ArrayList<>();

            cleanLock.readLock().lock();

            currentTarget.add(root);
            lstFilter.add(filter);

            for (int i = 0; i < size; i++) {
                for (int j = 0; j < currentTarget.size(); j++) {
                    current = currentTarget.get(j);
                    filter = lstFilter.get(j);
                    if (current.getMulti() != null) {
                        tempFilter = appendFilter(filter, "/", "#");
                        addResult(result, current.getMulti(), tempFilter);
                    }
                    if (current.getSingle() != null) {
                        tempFilter = appendFilter(filter, "/", "+");
                        if (i == size - 1) {
                            addResult(result, current.getSingle(), tempFilter);
                        } else {
                            tempTarget.add(current.getSingle());
                            tempLstFilter.add(tempFilter);
                        }
                    }
                    TreeNode<T> n;
                    n = current.getChilds().get(levels[i]);
                    if (n != null) {
                        tempFilter = appendFilter(filter, "/", levels[i]);
                        if (i == size - 1) {
                            addResult(result, n, tempFilter);
                            if (n.getMulti() != null) {
                                tempFilter = tempFilter + "/#";
                                addResult(result, n.getMulti(), tempFilter);
                            }
                        } else {
                            tempTarget.add(n);
                            tempLstFilter.add(tempFilter);
                        }
                    }
                }
                currentTarget.clear();
                ArrayList<TreeNode<T>> temp = currentTarget;
                currentTarget = tempTarget;
                tempTarget = temp;

                lstFilter.clear();
                ArrayList<String> t = lstFilter;
                lstFilter = tempLstFilter;
                tempLstFilter = t;
            }
            return result.keySet();
        } finally {
            cleanLock.readLock().unlock();
        }
    }

    private String appendFilter(String filter, String splitter, String appender) {
        String result;
        if (filter.length() == 0) {
            result = appender;
        } else {
            result = filter + splitter + appender;
        }
        return result;
    }

    private void addResult(final HashMap<ClientInfo<T>, Integer> result, final TreeNode<T> node, final String filter) {
        if (node.getClients() != null) {
            node.getClients().forEach((k, v) ->
            {
                ClientInfo<T> info = new ClientInfo<>(v, k);
                info.setFilter(filter);
                addResult(result, info);
            });
        }
    }

    /**
     * add match client to the result collection
     *
     * @param result the result that will be return to caller
     * @param info   the client that match the subscription filters
     */
    private void addResult(final HashMap<ClientInfo<T>, Integer> result, final ClientInfo<T> info) {
        if (!result.containsKey(info)) {
            result.put(info, info.getQos());
        } else {
            Integer qosOld = result.get(info);
            if (qosOld > info.qos) {
                result.remove(info);
                result.put(info, info.qos);
            }
        }
    }

    /**
     * remove all subscriptions of one client. may be the clientid is disconnected from the mqtt server
     *
     * @param clientId the client whose subscription should be removed
     */
    public int removeSubscribe(final T clientId) {
        int removeCount = 0;
        try {
            cleanLock.readLock().lock();
            synchronized (mapClient) {
                ConcurrentHashMap<String, Integer> map = mapClient.get(clientId);
                if (map != null) {
                    removeCount = map.size();
                    map.forEach((k, v) -> doRemoveSubscribe(clientId, k));
                    mapClient.remove(clientId);
                }
            }
        } finally {
            cleanLock.readLock().unlock();
        }
        return removeCount;
    }

    /**
     * remove one subscrion from the tree
     *
     * @param clientId    the client of the subscription
     * @param topicFilter the topic filter of the subscription
     */
    public void removeSubscribe(T clientId, String topicFilter) {
        try {
            cleanLock.readLock().lock();
            doRemoveSubscribe(clientId, topicFilter);
        } finally {
            cleanLock.readLock().unlock();
        }
    }

    /**
     * get the filter's Qos if it exists
     *
     * @param filter   the filter
     * @param clientId the client who subscribe the filter
     * @return if exist, it will return 0,1,2, otherwise it will return -1;
     */
    public int getQos(String filter, T clientId) {
        try {
            String[] levels = filter.split("/");
            int size = levels.length;
            TreeNode<T> current = root;
            cleanLock.readLock().lock();
            for (int i = 0; i < size; i++) {
                switch (levels[i]) {
                    case MULTI_LEVEL: {
                        if (current.getMulti() != null) {
                            if (i == size - 1) {
                                if (current.getMulti().getClients() != null) {
                                    Integer qos = current.getMulti().getClients().get(clientId);
                                    if (qos != null) {
                                        return qos;
                                    } else {
                                        return -1;
                                    }
                                }
                            }
                        }
                        return -1;
                    }
                    case SINGLE_LEVEL: {
                        if (current.getSingle() != null) {
                            if (i == size - 1) {
                                if (current.getSingle().getClients() != null) {
                                    Integer qos = current.getSingle().getClients().get(clientId);
                                    if (qos != null) {
                                        return qos;
                                    } else {
                                        return -1;
                                    }
                                }
                                return -1;
                            } else {
                                current = current.getSingle();
                            }
                        } else {
                            return -1;
                        }
                    }
                    break;
                    default: {
                        TreeNode<T> node = current.getChilds().get(levels[i]);
                        if (node != null) {
                            if (i == size - 1) {
                                if (node.getClients() != null) {
                                    Integer qos = node.getClients().get(clientId);
                                    if (qos != null) {
                                        return qos;
                                    } else {
                                        return -1;
                                    }
                                }
                                return -1;
                            }
                            current = node;
                        } else {
                            return -1;
                        }
                    }
                }
            }
        } finally {
            cleanLock.readLock().unlock();
        }
        return -1;
    }

    /**
     * check if the topicFilter is subscribe by some client. if exist, return true
     *
     * @param topicFilter the topic filter to be check
     * @return if the filter is subscibed by at least one client, then return true; else return false
     */
    public boolean isSubscribed(String topicFilter) {
        try {
            String[] levels = topicFilter.split("/");
            int size = levels.length;
            TreeNode<T> current = root;
            cleanLock.readLock().lock();
            for (int i = 0; i < size; i++) {
                switch (levels[i]) {
                    case MULTI_LEVEL: {
                        if (current.getMulti() != null) {
                            if (i == size - 1) {
                                if (current.getMulti().getClients() != null) {
                                    if (!current.getMulti().getClients().isEmpty()) {
                                        return true;
                                    }
                                }
                            }
                        }
                        return false;
                    }
                    case SINGLE_LEVEL: {
                        if (current.getSingle() != null) {
                            if (i == size - 1) {
                                if (current.getSingle().getClients() != null) {
                                    if (!current.getSingle().getClients().isEmpty()) {
                                        return true;
                                    }
                                }
                                return false;
                            } else {
                                current = current.getSingle();
                            }
                        } else {
                            return false;
                        }
                    }
                    break;
                    default: {
                        TreeNode<T> node = current.getChilds().get(levels[i]);
                        if (node != null) {
                            if (i == size - 1) {
                                if (node.getClients() != null) {
                                    if (!node.getClients().isEmpty()) {
                                        return true;
                                    }
                                }
                                return false;
                            }
                            current = node;
                        } else {
                            return false;
                        }
                    }
                }
            }
        } finally {
            cleanLock.readLock().unlock();
        }
        return false;
    }

    private void cleanSignal(TreeNode<T> parent, TreeNode<T> child) {
        if (child.getMulti() != null) {
            cleanMulty(child, child.getMulti());
        }
        if (child.getSingle() != null) {
            cleanSignal(child, child.getSingle());
        }
        if (child.getChilds() != null) {
            child.getChilds().forEach((s, treeNode) -> cleanChild(child, treeNode));
        }
        check(parent, child);
    }

    private void cleanMulty(TreeNode<T> parent, TreeNode<T> child) {
        if (child.getClients() == null) {
            parent.setMulti(null);
        } else if (child.getClients().isEmpty()) {
            parent.setMulti(null);
        }
    }

    private void cleanChild(TreeNode<T> parent, TreeNode<T> child) {
        if (child.getMulti() != null) {
            cleanMulty(child, child.getMulti());
        }

        if (child.getSingle() != null) {
            cleanSignal(child, child.getSingle());
        }

        if (child.getChilds() != null) {
            child.getChilds().forEach((s, treeNode) -> cleanChild(child, treeNode));
        }
        check(parent, child);
    }

    /**
     * check if the child hasn't any clients, if so, then remove the child from the parent
     *
     * @param parent
     * @param child
     */
    private void check(TreeNode<T> parent, TreeNode<T> child) {
        if (child.getMulti() == null
                && child.getSingle() == null
                && child.getChilds().isEmpty()) {
            if (child.getClients() == null) {
                parent.getChilds().remove(child.getToken());
            } else if (child.getClients().isEmpty()) {
                parent.getChilds().remove(child.getToken());
            }
        }
    }

    /**
     * clean the subscription tree, remove used leaf and tree node.
     * this method whill travels the tree with deep primary method, the calculation cost is expensive.
     * you should call the method carefully.
     */
    public void clean() {
        try {
            cleanLock.writeLock().lock();
            if (root.getMulti() != null) {
                cleanMulty(root, root.getMulti());
            }

            if (root.getSingle() != null) {
                cleanSignal(root, root.getSingle());
            }

            if (!root.getChilds().isEmpty()) {
                root.getChilds().forEach((s, treeNode) -> cleanChild(root, treeNode));
            }
        } finally {
            cleanLock.writeLock().unlock();
        }
    }

    /**
     * remove all subscription info
     *
     * @param node the root the tree node
     */
    private void clear(TreeNode<T> node) {
        if (node.getSingle() != null) {
            clear(node.getSingle());
            node.setSingle(null);
        }
        if (node.getMulti() != null) {
            clear(node.getMulti());
            node.setMulti(null);
        }
        if (node.getClients() != null) {
            node.getClients().clear();
            node.setClients(null);
        }
        if (!node.getChilds().isEmpty()) {
            node.getChilds().forEach((k, v) -> {
                clear(v);
            });
            node.getChilds().clear();
        }
    }

    /**
     * clear all subscription tree node
     */
    public void clear() {
        try {
            cleanLock.writeLock().lock();
            clear(root);
        } finally {
            cleanLock.writeLock().unlock();
        }

    }

    private void doRemoveSubscribe(T clientId, String topicFilter) {
        String[] levels = topicFilter.split("/");
        int size = levels.length;
        TreeNode<T> current = root;
        for (int i = 0; i < size; i++) {
            switch (levels[i]) {
                case MULTI_LEVEL: {
                    if (current.getMulti() == null) {
                        return;
                    }
                    if (i == size - 1) {
                        TreeNode<T> node = current.getMulti();
                        node.removeClient(clientId);
                        return;
                    } else {
                        return;
                    }
                }
                case SINGLE_LEVEL: {
                    if (current.getSingle() == null) {
                        return;
                    }
                    if (i == size - 1) {
                        TreeNode<T> node = current.getSingle();
                        node.removeClient(clientId);
                        return;
                    } else {
                        current = current.getSingle();
                    }
                }
                break;
                default: {
                    TreeNode<T> n = current.getChilds().get(levels[i]);
                    if (n != null) {
                        if (i == size - 1) {
                            n.removeClient(clientId);
                            return;
                        } else {
                            current = n;
                        }
                    } else {
                        return;
                    }
                }
            }
        }
    }


    /**
     * the client information
     * client : the mqtt client
     * qos: at most once, at least once, exactly once
     */
    public static class ClientInfo<T> {
        private int qos;
        private T client;
        private String filter;

        public ClientInfo() {
        }

        public ClientInfo(int qos, T client) {
            this.qos = qos;
            this.client = client;
        }

        @Override
        public String toString() {
            return "ClientInfo{" +
                    "qos=" + qos +
                    ", client='" + client + '\'' +
                    '}';
        }

        /**
         * 这里equals方法没考虑qos
         *
         * @param o
         * @return
         */
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            ClientInfo that = (ClientInfo) o;

            return client.equals(that.client);

        }

        @Override
        public int hashCode() {
            return client.hashCode();
        }

        public int getQos() {
            return qos;
        }

        public void setQos(int qos) {
            this.qos = qos;
        }

        public T getClient() {
            return client;
        }

        public void setClient(T client) {
            this.client = client;
        }

        public String getFilter() {
            return filter;
        }

        public void setFilter(String filter) {
            this.filter = filter;
        }
    }

    private static class TreeNode<T> {
        /**
         * the child tree node whose filter isn't '#' and '+'
         */
        private final ConcurrentHashMap<String, TreeNode<T>> childs = new ConcurrentHashMap<>(3);
        /**
         * the clients whose subscription topic filter end in this TreeNode
         */
        private ConcurrentHashMap<T, Integer> clients;
        /**
         * the partition of topic filter of this node
         */
        private String token;
        /**
         * the child node whose topic filter is '#'
         */
        private TreeNode<T> multi;
        /**
         * the child node whose topic filter is '+'
         */
        private TreeNode<T> single;

        private TreeNode(String token) {
            this.token = token;
        }

        private String getToken() {
            return token;
        }

        private void setToken(String token) {
            this.token = token;
        }

        private ConcurrentHashMap<T, Integer> getClients() {
            return clients;
        }

        public void setClients(ConcurrentHashMap<T, Integer> clients) {
            this.clients = clients;
        }

        private TreeNode<T> getMulti() {
            return multi;
        }

        private void setMulti(TreeNode<T> multi) {
            this.multi = multi;
        }

        private TreeNode<T> getSingle() {
            return single;
        }

        private void setSingle(TreeNode<T> single) {
            this.single = single;
        }

        private ConcurrentHashMap<String, TreeNode<T>> getChilds() {
            return childs;
        }

        private void removeClient(T client) {
            if (clients != null) {
                clients.remove(client);
            }
        }

        private void addClient(T clientId, int qos) {
            if (clients == null) {
                synchronized (this) {
                    if (clients == null) {
                        clients = new ConcurrentHashMap<>(3);
                    }
                }
            }
            clients.put(clientId, qos);
        }
    }
}
